// ObjectError.m
// 
//  (see .h for information)
//
// NO WARRANTY:
// ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE 
// IS HEREBY DISCLAIMED.  IN NO EVENT WILL THE AFOREMENTIONED PARTIES BE LIABLE 
// FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL 
// DAMAGES ARISING OUT OF THE USE OF OR INABILITY TO USE THIS CODE.

#import "ObjectError.h"
#import <appkit/Application.h>
#import <appkit/nextstd.h>
#import <appkit/appkit.h>
#import <strings.h>
#import <stdio.h>
#import <sys/signal.h>

typedef struct _sig  {
	int number;
	BOOL isOn;
	char *message;
} SignalItem;

typedef struct _type  {
	char encoding;
	char *format;
	char *name;
} ObjcType;

extern SignalItem signals[];
extern ObjcType encodings[];

@implementation ObjectError

static void handle_signal(int signal);
static BOOL ignoreCrashes;
static BOOL muddleOn;

+ setup
// Initialize everything and poseAs the Object class.
{
	[[self class] poseAs:[Object class]];
	[[self class] setSignalHandler:handle_signal];
	ignoreCrashes=NO;
	muddleOn=NO;
	return self;
}

+ setMuddleOn:(BOOL)flag
{
	muddleOn=flag;
	return self;
}

+ setSignalHandler:(void (*)())handler
{
	SignalItem *cur;
	
	for (cur=signals; cur->number; cur++)  {
		if (cur->isOn)  {
			signal(cur->number, handler);
		}
	}
	return self;
}

+ resumeHandlingCrashes
{
	[[self class] setSignalHandler:handle_signal];
	ignoreCrashes=NO;
	return self;
}

+ stopHandlingCrashes
{
	[[self class] setSignalHandler:(void (*)())SIG_DFL];
	ignoreCrashes=YES;
	return self;
}

//		ASSUMPTION:  The layout of a stack frame for a method invocation is:
//			fp+0 bytes:		calling frame
//			fp+4 bytes:		calling pc
//			fp+8 bytes:		self
//			fp+12 bytes:	selector for method invoked
//			fp+16 bytes:	first argument
//
//		ASSUMPTION: The layout of a stack frame for a function invocation is:
//			fp+0 bytes:		calling frame
//			fp+4 bytes:		calling pc
//			fp+8 bytes:		first argument
//
//		Clearly these are shady assumptions, however we're already
//		in the process of crashing, so what harm can be done?

#define MAX_FUNCTION_ARGS  4		// Print at most four args to a function

+ printFunctionFromFP:(void *)framePointer
{
	char buffer[256];
	char line[1024];
	void *argStart;
	long argNum;	// Index into arguments;
	
	sprintf(line, "function (");
	argStart = framePointer + 8;
	for (argNum=0; argNum<MAX_FUNCTION_ARGS; argNum++)  {
		sprintf(buffer, "%s0x%06lx", ((argNum != 0)?", ":""), 
					*(((unsigned long *)argStart)+argNum));
		strcat(line, buffer);
	}
	strcat(line, ")");
	NXLogError("%s\n", line);
	
	return self;
}

#define IS_CLASS(object)  ([object class] == object)

+ printMethodFromFP:(void *)framePointer
{
	char buffer[256];
	char line[1024];
	SEL selector;
	id object;
	Method m;
	BOOL isClassMethod;
	
	object = *(id *)(framePointer+8);  // receiver is 8 bytes from fp
	selector = *(SEL *)(framePointer+12);  // selector is 12 bytes from fp
	isClassMethod = IS_CLASS(object);
	
	sprintf(line, "%c[%s %s", (isClassMethod?'+':'-'), 
				object_getClassName(object), sel_getName(selector));
	
	m = (isClassMethod	? class_getClassMethod([object class], selector)
						: class_getInstanceMethod([object class], selector));
	
	if (m)  {
		void *argStart = (framePointer+8);
		int argNum, numArgs, offset;
		char *type;
		
		numArgs = method_getNumberOfArguments(m);
		argNum = 2;  // Skip the first two args which are self and _cmd
		while (argNum<numArgs)  {
			ObjcType *cur;
			
			method_getArgumentInfo(m, argNum, &type, &offset);
			for (cur=encodings; cur->encoding; cur++)  {
				// Find the ObjcType
				if (cur->encoding == type[0])  {
					sprintf(buffer, " :(%s)", cur->name);
					strcat(line, buffer);
					sprintf(buffer, cur->format, *(long *)(argStart+offset));
					strcat(line, buffer);
					break;
				}
			}
			argNum++;
		}
	}  else  {
		strcat(line, " Unknown method");
	}
	strcat(line, "]");
	NXLogError("%s\n", line);
	
	return self;
}

#define MAX_FRAMES 50

+ printBacktrace
{
	void *framePointer;				// pointer to current frame
	unsigned int frameCount;		// counter for number of frames printed
	
	[self stopHandlingCrashes];		// Try to avoid re-entry problems
	
	// Start the frame pointer off at our frame
	framePointer = ((void *) &self)-8;
	frameCount=0;
	
	// Assume that a whole lotta frames means either (a) we're trashed or
	// (b) we're in a recursive deathtrap.  In the latter case, we've
	// probably got enough info to see a whole cycle.
	
	while (frameCount<MAX_FRAMES && framePointer)  {
		// If this frame is a method call we'll have a valid 
		// selector at (fp+12).
		if (sel_isMapped(*(SEL *)(framePointer+12)))  {
			[self printMethodFromFP:framePointer];
		}  else  {
			[self printFunctionFromFP:framePointer];
		}
		framePointer = (void *)*(long *)framePointer;  // go up one frame
	}
	return self;
}

+ dumpBacktrace:(const char *)message
{
	NXLogError("%s\n", message);
	NXLogError("Here's the stack frame Backtrace:\n");
	[self printBacktrace];
	return self;
}

static void handle_signal(int signal)
{
	const char *msg=NULL;
	char buf[256];
	SignalItem *cur;
	
	msg = "Unrecognized signal";
	for (cur = signals; cur->number; cur++)  {
		if (cur->number==signal)  {
			msg = cur->message;
			break;
		}
	}
	sprintf(buf, "Caught signal #%d: \"%s\"", signal, msg);
	[ObjectError dumpBacktrace:buf];
	if (!muddleOn)  {
		exit(1);
	}
	[ObjectError tryToContinue];
}

- error:(const char *)aString, ...
{
	va_list ap;
	char buffer[1024];
	
	va_start(ap, aString);
	vsprintf(buffer, aString, ap);
	va_end(ap);
	
	if (!ignoreCrashes)  [[self class] dumpBacktrace:buffer];
	if (muddleOn)
	{
		[[self class] tryToContinue];
		return self;
	}
	return [super error:buffer];
}

+ error:(const char *)aString, ...
{
	va_list ap;
	char buffer[1024];
	
	va_start(ap, aString);
	vsprintf(buffer, aString, ap);
	va_end(ap);
	
	if (!ignoreCrashes)  [[self class] dumpBacktrace:buffer];
	if (muddleOn)
	{
		[self tryToContinue];
		return self;
	}
	return [super error:buffer];
}

+ tryToContinue;
{
	ignoreCrashes = NO;
	NXLogError("Trying to continue...\n");
	NXRunAlertPanel("Internal Error","An internal error has occurred.  Try to save work, then quit and restart the application.","Continue",NULL,NULL);
	[NXApp abortModal];
	return self;
}

#define ON		1
#define OFF		0

SignalItem signals[] = {
	{SIGHUP, 	OFF, 	"Hangup"},
	{SIGINT, 	OFF, 	"Interrupt"},
	{SIGQUIT, 	ON, 	"Quit"},
	{SIGILL, 	ON, 	"Illegal instruction"},
	{SIGTRAP, 	ON, 	"Trace trap"},
	{SIGIOT, 	ON, 	"IOT instruction"},
	{SIGEMT, 	ON, 	"EMT instruction"},
	{SIGFPE, 	ON, 	"Floating point exception"},
	{SIGKILL, 	OFF, 	"Kill"},
	{SIGBUS, 	ON, 	"Bus error"},
	{SIGSEGV, 	ON, 	"Segmentation violation"},
	{SIGSYS, 	ON, 	"Bad argument to system call"},
	{SIGPIPE, 	OFF, 	"Write on a pipe with no one to read it"},
	{SIGALRM, 	OFF, 	"Alarm clock"},
	{SIGTERM, 	OFF, 	"Software termination"},
	{SIGURG, 	OFF, 	"Urgent condition present on socket"},
	{SIGSTOP, 	OFF, 	"Stop"},
	{SIGTSTP, 	OFF, 	"Stop signal generated from keyboard"},
	{SIGCONT, 	OFF, 	"Continue after stop"},
	{SIGCHLD, 	OFF, 	"Child status changed"},
	{SIGTTIN, 	OFF, 	"Background read attempted from control terminal"},
	{SIGTTOU, 	OFF, 	"Background write attempted to control terminal"},
	{SIGIO, 	OFF, 	"I/O is possible on a descriptor"},
	{SIGXCPU, 	OFF, 	"CPU time limit is exceeded"},
	{SIGXFSZ, 	OFF, 	"File size limit exceeded"},
	{SIGVTALRM, OFF, 	"Virtual timer alarm"},
	{SIGPROF, 	OFF, 	"Profiling timer alarm"},
	{SIGWINCH, 	OFF, 	"Window size change"},
	{SIGUSR1, 	OFF, 	"User defined signal 1"},
	{SIGUSR2, 	OFF, 	"User defined signal 2"},
	{0},
};

ObjcType encodings[] = {
	{_C_ID,			"0x%06lx",		"id"},
	{_C_CLASS,		"0x%06lx",		"Class"},
	{_C_SEL,		"%s",			"SEL"},
	{_C_CHR,		"%c",			"char"},
	{_C_UCHR,		"%c",			"unsigned char"},
	{_C_SHT,		"%s",			"short"},
	{_C_USHT,		"%c",			"unsigned short"},
	{_C_INT,		"%d",			"int"},
	{_C_UINT,		"%d",			"unsigned int"},
	{_C_LNG,		"%l",			"long"},
	{_C_ULNG,		"%l",			"unsigned long"},
	{_C_FLT,		"%f",			"float"},
	{_C_DBL,		"%f",			"double"},
	{_C_VOID,		"0x%06lx",		"void"},
	{_C_PTR,		"0x%06lx",		"ptr"},
	{_C_CHARPTR,	"%s",			"char *"},
	{_C_STRUCT_B,	"%x",			"struct"},
	{0,				"0x%06lx",		"unknown type"},
};

@end
